#include "session.h"
#include <stdint.h>
#include <asm/delay.h>
#include <uapi/time.h>
#include <uapi/stdlib.h>
#include <uapi/string.h>
#include "session_global.h"
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include "ff/ff.h"
#else
#include <mve/cv/cv_global.h>
#include "ff.h"
#endif


///////////////////////////////////////////////////////////////////////////////
#define UINT32T_MAX	0xffffffffU // UINT32_MAX of stdint.h 4294967295

///////////////////////////////////////////////////////////////////////////////
enum AudioObjectType {
    AOT_NULL,
                   // Support?                  Name
    AOT_AAC_MAIN,  ///< Y                       Main
    AOT_AAC_LC,    ///< Y                       Low Complexity
    AOT_AAC_SSR,   ///< N (code in SoC repo)    Scalable Sample Rate
    AOT_AAC_LTP,   ///< Y                       Long Term Prediction
	//......
};

///////////////////////////////////////////////////////////////////////////////
const static int avpriv_mpeg4audio_sample_rates[16] = {
    96000, 88200, 64000, 48000, 44100, 32000,
    24000, 22050, 16000, 12000, 11025, 8000, 7350
};

const static uint8_t ff_mpeg4audio_channels[8] = {
    0, 1, 2, 3, 4, 5, 6, 8
};

///////////////////////////////////////////////////////////////////////////////
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static inline uint32_t SysCcountToMs(uint32_t cnt)
{
	return cnt / (1000 * CONFIG_PLATFORM_CLK_M);
}
#endif

static
int get_sample_rate_index(int sample_rate)
{
	int i;

	for (i = 0; i < sizeof(avpriv_mpeg4audio_sample_rates); i++) {
		if (sample_rate == avpriv_mpeg4audio_sample_rates[i])
			break;
	}
	if (i >= sizeof(avpriv_mpeg4audio_sample_rates))
		i = 4;

	return i;
}

///////////////////////////////////////////////////////////////////////////////
/*!
 * \brief copy data from src to dst
 */
int media_io_write_packet(void *h, media_bsf_opt_t opt, void *state, phys_addr_t outbuf, int32_t *olen, phys_addr_t inbuf, int32_t ilen)
{
	char *ibuf = (char *)inbuf;
	char *obuf = (char *)outbuf;

	if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_CLICE_PURE_DATA)
		|| (opt == GET_PACKET_DATA)) {
		memcpy(obuf, ibuf, ilen);
		*olen = ilen;
	}

	return 0;
}

/*!
 * \brief write aac adts header and packet data from mp4 mdat
 *
 * adts_write_packet() of ffmpeg adtsenc.c
 */
int32_t media_adts_aac_write_packet(void *h, media_bsf_opt_t opt, void *state, phys_addr_t outbuf, int32_t *olen,
						  phys_addr_t inbuf, int32_t ilen)
{
	aacconfig_t *aaccfg = h;
	char *ibuf = (char *)inbuf;
	char *obuf = (char *)outbuf;
	uint32_t full_frame_size = ADTS_HEADER_SIZE + ilen;
	unsigned char channel_conf = aaccfg->channel;
	unsigned char objecttype = 0x1;         //01 is AudioObjectType - 1, AAC_LC
	unsigned char sample_rate_index = get_sample_rate_index(aaccfg->sample_rate); //FIXME: use the real config of mp4/mkv file


	if (opt == GET_CLICE_PURE_DATA) {
		memcpy(obuf, ibuf, ilen);
		*olen = ilen;
		return 0;
	}

	if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_PACKET_DATA)) {
		/* 12 bits 0xfff, syncword*/
		obuf[0] = 0xff;
		obuf[1] = 0xf << 4;

		/* 1 bit 0, ID */
		/* 2 bits 0, layer */
		/* 1 bit 1, protection_absent */
		obuf[1] |= 0x01;

		/* 2 bits 01, objecttype */ //FIXME:
		/* 4 bits sample_rate_index, 0100 */ //FIXME:
		/* 1 bit 0 private_bit */
		/* 1 of 3 bits 010, channel_conf */ //FIXME:
		obuf[2] = (objecttype << 6) | (sample_rate_index << 2) | (channel_conf >> 2);

		/* 2 0f 3 bits 010, channel_conf */ //FIXME:
		/* 1 bit 0, original_copy */
		/* 1 bit 0, home */
		/* 1 bit 0, copyright_identification_bit */
		/* 1 bit 0, copyright_identification_start */
		/* 2 of 13 bits xxxx, full_frame_size */
		obuf[3] = (channel_conf << 6) | (full_frame_size >> (13 - 2));

		/* 8 0f 13 bits xxxx, full_frame_size */
		obuf[4] = ((full_frame_size >> 3) & 0xFF);

		/* 3 0f 13 bits xxxx, full_frame_size */
		/* 5 of 11 bits 0x7ff, adts_buffer_fullness */
		obuf[5] = (full_frame_size & 0x7) << 5;
		obuf[5] |= (0x7FF >> (11 - 5));

		/* 6 of 11 bits 0x7ff, adts_buffer_fullness */
		/* 2 bits 0, number_of_raw_data_blocks_in_frame */
		obuf[6] = (0x7ff >> (11 - 8)) & 0xFC;

		*olen += ADTS_HEADER_SIZE;
		memcpy(obuf + ADTS_HEADER_SIZE, ibuf, ilen);
		*olen += ilen;
	}

	return 0;
}

int media_mpeg4_to_m4v(void *h, media_bsf_opt_t opt, void *state, phys_addr_t outbuf, int32_t *olen, phys_addr_t inbuf, int32_t ilen)
{
	videoconfig_t *cfg = h;
	char *ibuf = (char *)inbuf;
	char *obuf = (char *)outbuf;

	if (opt == GET_CLICE_PURE_DATA) {
		memcpy(obuf, ibuf, ilen);
		*olen = ilen;
		return 0;
	}

	if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_PACKET_DATA)) {
		memcpy(obuf, cfg->metadata, cfg->metadata_len);

		*olen += cfg->metadata_len;
		memcpy(obuf + cfg->metadata_len, ibuf, ilen);
		*olen += ilen;
	}

	return 0;
}

/*!
 * \brief bit stream filter, from mp4 mdat to h264 stream
 *
 * h264_mp4toannexb_filter() of ffmpeg h264_mp4toannexb_bsf.c
 *
 * \param[in] h		metadata buffer
 * \param[in] opt	data type option
 * \param[in] state	nal unit length
 */
int32_t media_bsf_h264toannexb(void *h, media_bsf_opt_t opt, void *state,
					 phys_addr_t outbuf, int32_t *olen,
					 phys_addr_t inbuf, int32_t ilen)
{
	videoconfig_t *cfg = h;
	nalustate_t *nalstate = state;
	char *input_buf = (char *)inbuf;
	uint32_t tmp;
	unsigned char unit_type;
	unsigned char sps_seen, pps_seen, new_idr, length_size = 4;
	uint64_t out_size = 0;
	uint32_t start_code4 = 0x01000000;
	char start_code3[3] = {0x00, 0x00, 0x01};
	char *codec_private = NULL;
	int private_len = 0;
	unsigned char *ibuf = (unsigned char *)inbuf;
	unsigned char *obuf = (unsigned char *)outbuf;
	uint32_t nal_size;
	int i;

	if (opt == GET_CLICE_PURE_DATA) {
		memcpy(obuf, ibuf, ilen);
		*olen = ilen;
		return 0;
	}

	//NOTE: got extradata form stsd box of mp4
	if (cfg->metadata_len) {
		codec_private = cfg->metadata;
		private_len = cfg->metadata_len;
	}

	if ((opt == GET_PACKET_LEN) || (opt == GET_PACKET_DATA)
		|| (opt == GET_FIRST_SLICE_LEN) || (opt == GET_FIRST_SLICE_DATA)) {
		new_idr = 1;
		sps_seen = 0;
		pps_seen = 0;
	} else {
		new_idr = nalstate->new_idr;
		pps_seen = nalstate->pps_seen;
		sps_seen = nalstate->sps_seen;
	}

	if (cfg->mkv_block_len_flag)
		length_size = 3;

	*olen = 0;
	do {
		nal_size = 0;
		/* get length */
		tmp = 0;
		if (length_size == 4) {
			memcpy(&tmp, ibuf, length_size);
			ibuf += length_size;
			nal_size = uswap_32(tmp);
		} else {
			for (i = 0; i < length_size; i++) {
				tmp <<= 8;
				memcpy(&tmp, ibuf, 1);
				ibuf++;
			}
			nal_size = tmp;
		}
		length_size = 4;

		if (!nal_size || (nal_size > nalstate->total_len)) {
			debug("nal_size: %d(0x%08x) error!, tmp: 0x%08x, totallen:%d\n",
				nal_size, nal_size, tmp, nalstate->total_len);
			*olen = 0;
			return -1;
		}

		/* get h264 NAL type */
		unit_type = *ibuf & 0x1f;
		if (unit_type == H264_NAL_SPS) {
			sps_seen = new_idr = 1;
		} else if (unit_type == H264_NAL_PPS) {
			pps_seen = new_idr = 1;
			/* if SPS has not been seen yet, prepend the AVCC one to PPS */
			if (!sps_seen) {
				//FIXME: only write sps with sps_size, should get sps part form extradata
				sps_seen = 1;
				if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
					|| (opt == GET_OTHER_CLICE_DATA)) {
					memcpy(obuf, codec_private, private_len);
					obuf += private_len;
				}
				*olen += private_len;
			}
		}

		/* If this is a new IDR picture following an IDR picture, reset the idr flag.
			* Just check first_mb_in_slice to be 0 as this is the simplest solution.
			* This could be checking idr_pic_id instead, but would complexify the parsing. */
		if (!new_idr && unit_type == H264_NAL_IDR_SLICE && (ibuf[1] & 0x80))
			new_idr = 1;

		/* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */
		if (new_idr && unit_type == H264_NAL_IDR_SLICE && !sps_seen && !pps_seen) {
			if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
				|| (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, codec_private, private_len);
				obuf += private_len;
			}
			*olen += private_len;
			new_idr = 0;
		} else if (new_idr && unit_type == H264_NAL_IDR_SLICE && sps_seen && !pps_seen) {
			//FIXME: only write sps with sps_size, should get sps part form extradata
			if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
				|| (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, codec_private, private_len);
				obuf += private_len;
			}
			*olen += private_len;
		}

		if (out_size && !(unit_type == H264_NAL_SPS || unit_type == H264_NAL_PPS)) {
			if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
				|| (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, start_code3, sizeof(start_code3));
				obuf += sizeof(start_code3);
				if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
					memcpy(obuf, ibuf, ilen - sizeof(tmp));
					obuf += (ilen - sizeof(tmp));
				} else {
					memcpy(obuf, ibuf, nal_size);
					obuf += nal_size;
				}
			}
			*olen += sizeof(start_code3);
			if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
				*olen += (ilen - sizeof(tmp));
				ibuf += (ilen - sizeof(tmp));
			} else {
				*olen += nal_size;
				ibuf += nal_size;
			}
		}
		if ((unit_type == H264_NAL_SPS || unit_type == H264_NAL_PPS)
			|| (!(unit_type == H264_NAL_SPS || unit_type == H264_NAL_PPS)
				&& !out_size)) {
			if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
				|| (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, &start_code4, sizeof(start_code4));
				obuf += sizeof(start_code4);
				if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
					memcpy(obuf, ibuf, ilen - sizeof(tmp));
					obuf += (ilen - sizeof(tmp));
				} else {
					memcpy(obuf, ibuf, nal_size);
					obuf += nal_size;
				}
			}
			*olen += sizeof(start_code4);
			if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
				*olen += (ilen - sizeof(tmp));
				ibuf += (ilen - sizeof(tmp));
			} else {
				*olen += nal_size;
				ibuf += nal_size;
			}
		}

		if (!new_idr && unit_type == H264_NAL_SLICE) {
			new_idr  = 1;
			sps_seen = 0;
			pps_seen = 0;
		}
	} while ((ibuf < (input_buf + ilen))
			 && ((opt == GET_PACKET_LEN) || (opt == GET_PACKET_DATA)));

	nalstate->new_idr = new_idr;
	nalstate->pps_seen = pps_seen;
	nalstate->sps_seen = sps_seen;
	nalstate->nallen = nal_size;

	return *olen;
}

/*!
 * \brief bit stream filter, from mp4 mdat to hevc/h265 stream
 */
int32_t media_bsf_hevctoannexb(void *h, media_bsf_opt_t opt, void *state, phys_addr_t outbuf,
					 int32_t *olen, phys_addr_t inbuf, int32_t ilen)
{
	videoconfig_t *cfg = h;
	nalustate_t *nalstate = state;
	uint32_t tmp;
	int got_irap;
	uint32_t start_code4 = 0x01000000;
	char *extradata = NULL;
	int extradata_len = 0;
	char *ibuf = (char *)inbuf;
	char *obuf = (char *)outbuf;
	uint32_t nalu_size = 0;
	int32_t nalu_type;
	int32_t is_irap, add_extradata, extra_size;

	if (opt == GET_CLICE_PURE_DATA) {
		memcpy(obuf, ibuf, ilen);
		*olen = ilen;
		return 0;
	}

	//NOTE: got extradata from stsd box of mp4
	if (cfg->metadata_len) {
		extradata = cfg->metadata;
		extradata_len = cfg->metadata_len;
	}
	if ((opt == GET_PACKET_LEN) || (opt == GET_PACKET_DATA)
		|| (opt == GET_FIRST_SLICE_LEN) || (opt == GET_FIRST_SLICE_DATA)) {
		got_irap = 0;
	} else
		got_irap = nalstate->got_irap;

	*olen = 0;

	do {
		memcpy(&tmp, ibuf, sizeof(tmp));
		nalu_size = uswap_32(tmp);
		if (!nalu_size || (nalu_size > nalstate->total_len)) {
			debug("nal_size: %d(0x%08x) error!, tmp: 0x%08x, totallen:%d\n",
				nalu_size, nalu_size, tmp, nalstate->total_len);
			*olen = 0;
			return -1;
		}
		ilen -= sizeof(tmp);
		ibuf += sizeof(tmp);
		nalu_type = (ibuf[0] >> 1) & 0x3f;

		/* prepend extradata to IRAP frames */
		is_irap       = nalu_type >= 16 && nalu_type <= 23;
		add_extradata = is_irap && !got_irap;
		extra_size    = add_extradata * extradata_len;
		got_irap     |= is_irap;

		if (extra_size) {
			if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
				|| (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, extradata, extradata_len);
				obuf += extradata_len;
			}
			*olen += extradata_len;
		}

		if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
			|| (opt == GET_OTHER_CLICE_DATA)) {
			memcpy(obuf, &start_code4, sizeof(start_code4));
			obuf += sizeof(start_code4);
		}
		*olen += sizeof(start_code4);

		if ((opt == GET_PACKET_DATA) || (opt == GET_FIRST_SLICE_DATA)
			|| (opt == GET_OTHER_CLICE_DATA)) {
			if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
				memcpy(obuf, ibuf, ilen);
				obuf += ilen;
			} else {
				memcpy(obuf, ibuf, nalu_size);
				obuf += nalu_size;
			}
		}
		if ((opt == GET_FIRST_SLICE_DATA) || (opt == GET_OTHER_CLICE_DATA)) {
			*olen += ilen;
			ibuf += ilen;
			ilen -= ilen;
		} else {
			*olen += nalu_size;
			ibuf += nalu_size;
			ilen -= nalu_size;
		}
	} while (ilen && ((opt == GET_PACKET_LEN) || (opt == GET_PACKET_DATA)));

	nalstate->got_irap = got_irap;
	nalstate->nallen = nalu_size;

	return *olen;
}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#define CONFIG_PLATFORM_CLK_M 10
uint32_t xthal_get_ccount(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (ts.tv_sec * 1000000 * CONFIG_PLATFORM_CLK_M + ts.tv_nsec / 1000 * CONFIG_PLATFORM_CLK_M);
}

static inline uint32_t SysCcountToMs(uint32_t cnt)
{
	return cnt / (1000 * CONFIG_PLATFORM_CLK_M);
}

uint32_t media_SysGetMs(void)
#else
inline uint32_t media_SysGetMs(void)
#endif
{
	uint32_t cnt = xthal_get_ccount();
	return SysCcountToMs(cnt);
}

int media_get_ms_diff(uint32_t before)
{
	int ret;
	uint32_t diff = 0;
	uint32_t cnt = xthal_get_ccount();

	if (cnt >= before)
		diff = cnt - before;
	else
		diff = cnt + UINT32T_MAX - before;
	ret = diff / (1000 * CONFIG_PLATFORM_CLK_M);

	return ret;
}

int media_flush_time(media_time_t *time, media_time_t *sys_time)
{
	uint32_t diff_ms;

	if (sys_time == NULL) {
		diff_ms = media_get_ms_diff(time->last_tick);
		time->last_tick = xthal_get_ccount();
		diff_ms += time->last_ms;
		time->last_second += (diff_ms / 1000);
		time->last_ms = diff_ms % 1000;
	} else {
		diff_ms = media_get_ms_diff(sys_time->last_tick);
		time->last_tick = xthal_get_ccount();
		diff_ms += sys_time->last_ms;
		time->last_second = sys_time->last_second + diff_ms / 1000;
		time->last_ms = diff_ms % 1000;
	}

	return 0;
}

int media_get_ms_from_systime(media_time_t *time, media_time_t *sys_time)
{
	int ret;

	//NOTE: return value is max 49.7 days record valid
	ret = sys_time->last_second * 1000 + sys_time->last_ms
			- (time->last_second * 1000 + time->last_ms);

	return ret;
}
